<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Java Swing model architecture</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Java, Swing, MVC, modified MVC, model view controller, architecture, tutorial, programming, Linux">
<meta name="description" content="This part of the Java Swing tutorial, we cover 
Java Swing model architecture.">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href=".." title="Home">Contents</a>


<h1>Java Swing model architecture</h1>


<p>
Swing engineers created the Swing toolkit implementing a modified Model 
View Controller design pattern. This enables efficient handling of data 
and using pluggable look and feel at runtime. 
</p>

<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* NewSquare */
google_ad_slot = "0364418177";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script>

<p>
The traditional MVC pattern divides an application into three parts. A model, 
a view and a cotroller. The model represents the data in the application. 
The view is the visual representation of the data. And finally the
controller processes and responds to events, typically user actions, and may 
invoke changes on the model. The idea is to separate the data access and business 
logic from data presentation and user interaction, by introducing an intermediate 
component: the controller.
</p>

<p>
The Swing toolkit uses a modified MVC design pattern. The Swing has single 
<b>UI object</b> for both the view and the controller.
This modified MVC is sometimes called a <b>separable model architecture</b>.
</p>

<p>
In the Swing toolkit, every component has it's model. Even the basic ones 
like buttons. There are two kinds of models in Swing toolkit.
</p>

<ul>
<li>state models</li>
<li>data models</li>
</ul>

<p>
The state models handle the state of the component. For example the model 
keeps track whether the component is selected or pressed. The data models 
handle data, they work with. A list component keeps a list of items, it is displaying. 
</p>


<p>
For Swing developer it means, that we often need to get a model instance 
in order to manipulate the data in the component. 
But there are exceptions. For convenience, there are some methods that 
return data without the model.
</p>

<pre class="code">
public int getValue() { 
    return getModel().getValue(); 
}
</pre>

<p>
For example the <code>getValue()</code> method of the 
<code>JSlider</code> component. 
The developer does not need to work with the model directly. Instead, 
the access to the model is done behind the scenes.
It would be an overkill to work with models directly in such simple 
situations. Because of this, the Swing tookit provides some 
convenience methods like the previous one.
</p>

<p>
To query the state of the model, we have two kinds of notifications.
</p>

<ul>
<li>lightweight notification</li>
<li>stateful notification</li>
</ul>


<p>
The lightweight notification uses a <code>ChangeListener</code> 
class. We have only one single event (<code>ChangeEvent)</code>
for all notifications coming from the component. For more complicated 
components, the stateful notification is used.
For such notifications, we have different kinds of events. For example 
the <code>JList</code> component has <code>ListDataEvent</code> 
and <code>ListSelectionEvent</code>. 
</p>

<p>
If we do not set a model for a component, a default one is created. 
For example the button component has <code>DefaultButtonModel</code> model
</p>

<pre class="code">
public JButton(String text, Icon icon) {
  // Create the model
  setModel(new DefaultButtonModel());

  // initialize
  init(text, icon);
}
</pre>

<p>
If we look at the JButton.java source file, we find out, that the default 
model is created at the construction of the component. 
</p>


<h2>ButtonModel</h2>

<p>
The model is used for various kinds of buttons like push buttons, check boxes, 
radio boxes and for menu items.
The following example illustrates the model for a <code>JButton</code>. 
We can manage only the state of the button, because no data can be associated 
with a push button. 
</p>

<pre class="code">
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class ButtonModel extends JFrame {

    private JButton ok;
    private JLabel enabled;
    private JLabel pressed;
    private JLabel armed;

    public ButtonModel() {

        setTitle("ButtonModel");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        ok = new JButton("ok");
        JCheckBox cb = new JCheckBox("Enabled", true);

        ok.setBounds(40, 30, 80, 25);
        ok.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {

            DefaultButtonModel model = (DefaultButtonModel) ok.getModel();
            if (model.isEnabled())
                enabled.setText("Enabled: true");
            else
                enabled.setText("Enabled: false");

            if (model.isArmed())
                armed.setText("Armed: true");
            else
                armed.setText("Armed: false");

            if (model.isPressed())
                pressed.setText("Pressed: true");
            else
                pressed.setText("Pressed: false");
            }

        });

        cb.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (ok.isEnabled())
                    ok.setEnabled(false);
                else 
                    ok.setEnabled(true);
            }
        });

        cb.setBounds(180, 30, 100, 25);

        enabled = new JLabel("Enabled: true");
        enabled.setBounds(40, 90, 90, 25);
        pressed = new JLabel("Pressed: false");
        pressed.setBounds(40, 120, 90, 25);
        armed  = new JLabel("Armed: false");
        armed.setBounds(40, 150, 90, 25);

        panel.add(ok);
        panel.add(cb);
        panel.add(enabled);
        panel.add(pressed);
        panel.add(armed);

        add(panel);

        setSize(350, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new ButtonModel();
    }
}
</pre>


<p>
In our example, we have a button, check box and three labels. The labels 
represent three properties of the button.
Whether it is pressed, disabled or armed.
</p>


<pre class="explanation">
ok.addChangeListener(new ChangeListener() {
</pre>

<p>
We use a lightweight <code>ChangeListener</code> to listen 
for button state changes.
</p>

<pre class="explanation">
DefaultButtonModel model = (DefaultButtonModel) ok.getModel();
</pre>

<p>
Here we get the default button model.
</p>

<pre class="explanation">
if (model.isEnabled())
    enabled.setText("Enabled: true");
else
    enabled.setText("Enabled: false");
</pre>

<p>
We query the model, whether the button is enabled or not. We update 
the label accordingly.
</p>

<pre class="explanation">
 if (ok.isEnabled())
     ok.setEnabled(false);
 else 
     ok.setEnabled(true);
</pre>

<p>
The check box enables or disables the button. To enable the ok button, 
we call the <code>setEnable()</code>
method. So we change the state of the button. Where is the model? The 
answer lies in the AbstractButton.java file. 
</p>

<pre class="code">
 public void setEnabled(boolean b) {
     if (!b &amp;&amp; model.isRollover()) {
         model.setRollover(false);
     } 
     super.setEnabled(b);
     model.setEnabled(b);
 }
</pre>

<p>
The answer is, that internally, we the Swing toolkit works with a model. 
The <code>setEnable()</code> is another convenience method for programmers. 
</p>


<img src="/img/gui/javaswing/buttonmodel.png" alt="ButtonModel">
<div class="figure">Figure: ButtonModel</div>


<h2>Custom ButtonModel</h2>

<p>
In the previous example, we used a default button model. In the following 
code example we will use our own 
button model. 
</p>

<pre class="code">
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class ButtonModel2 extends JFrame {

    private JButton ok;
    private JLabel enabled;
    private JLabel pressed;
    private JLabel armed;

    public ButtonModel2() {

        setTitle("ButtonModel");

        JPanel panel = new JPanel();
        panel.setLayout(null);

        ok = new JButton("ok");
        JCheckBox cb = new JCheckBox("Enabled", true);

        ok.setBounds(40, 30, 80, 25);

        cb.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (ok.isEnabled())
                    ok.setEnabled(false);
                else 
                    ok.setEnabled(true);
            }
        });

        cb.setBounds(180, 30, 100, 25);

        enabled = new JLabel("Enabled: true");
        enabled.setBounds(40, 90, 90, 25);
        pressed = new JLabel("Pressed: false");
        pressed.setBounds(40, 120, 90, 25);
        armed  = new JLabel("Armed: false");
        armed.setBounds(40, 150, 90, 25);

        ButtonModel model = new DefaultButtonModel() {
            public void setEnabled(boolean b) {
                if (b)
                    enabled.setText("Pressed: true");
                else 
                    enabled.setText("Pressed: false");

                super.setEnabled(b);
            }

            public void setArmed(boolean b) {
                if (b)
                    armed.setText("Armed: true");
                else
                    armed.setText("Armed: false");

                super.setArmed(b);
            }

            public void setPressed(boolean b) {
                if (b)
                    pressed.setText("Pressed: true");
                else 
                    pressed.setText("Pressed: false");

                super.setPressed(b);
            }

        };

        ok.setModel(model);

        panel.add(ok);
        panel.add(cb);
        panel.add(enabled);
        panel.add(pressed);
        panel.add(armed);

        add(panel);

        setSize(350, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new ButtonModel2();
    }
}
</pre>

<p>
This example does the same thing as the previous one. The difference is 
that we don't use a change listener and
we use a custom button model. 
</p>

<pre class="explanation">
ButtonModel model = new DefaultButtonModel() {
</pre>

<p>
We create a button model and overwrite the necessary methods. 
</p>

<pre class="explanation">
public void setEnabled(boolean b) {
    if (b)
        enabled.setText("Pressed: true");
    else 
        enabled.setText("Pressed: false");

    super.setEnabled(b);
}
</pre>

<p>
We overwrite the <code>setEnabled()</code> method and add some 
functionality there. We must not forget to call the parent method as well 
to procede with the processing. 
</p>


<pre class="explanation">
ok.setModel(model);
</pre>

<p>
We set the custom model for the button.
</p>


<h2>JList models</h2>

<p>
Several components have two models. The <code>JList</code> 
component has the following models:
<code>ListModel</code> and <code>ListSelectionModel</code>. 
The ListModel handles data. And the
ListSelectionModel works with the GUI. The following example shows both models. 
</p>

<pre class="code">
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;


public class List extends JFrame {

    private DefaultListModel model;
    private JList list;

    public List() {

        setTitle("JList models");

        model = new DefaultListModel();
        model.addElement("Amelie");
        model.addElement("Aguirre, der Zorn Gottes");
        model.addElement("Fargo");
        model.addElement("Exorcist");
        model.addElement("Schindler list");

        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));

        JPanel leftPanel = new JPanel();
        JPanel rightPanel = new JPanel();

        leftPanel.setLayout(new BorderLayout());
        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));

        list = new JList(model);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));

        list.addMouseListener(new MouseAdapter() {

            public void mouseClicked(MouseEvent e) {
                if(e.getClickCount() == 2){
                  int index = list.locationToIndex(e.getPoint());
                  Object item = model.getElementAt(index);
                  String text = JOptionPane.showInputDialog("Rename item", item);
                  String newitem = null;
                  if (text != null) 
                     newitem = text.trim();
                  else 
                     return;

                  if (!newitem.isEmpty()) {
                    model.remove(index);
                    model.add(index, newitem);
                    ListSelectionModel selmodel = list.getSelectionModel();
                    selmodel.setLeadSelectionIndex(index);
                  }
                }
            }

        });

        JScrollPane pane = new JScrollPane();
        pane.getViewport().add(list);  
        leftPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        leftPanel.add(pane);

        JButton removeall = new JButton("Remove All");
        JButton add = new JButton("Add");
        add.setMaximumSize(removeall.getMaximumSize());
        JButton rename = new JButton("Rename");
        rename.setMaximumSize(removeall.getMaximumSize());
        JButton delete = new JButton("Delete");
        delete.setMaximumSize(removeall.getMaximumSize());

        add.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String text = JOptionPane.showInputDialog("Add a new item");
                String item = null;

                if (text != null) 
                    item = text.trim();
                else 
                    return;

                if (!item.isEmpty())
                    model.addElement(item);
            }
        });

        delete.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index >= 0)
                    model.remove(index);
            }

        });

        rename.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index == -1) return;
                Object item = model.getElementAt(index);
                String text = JOptionPane.showInputDialog("Rename item", item);
                String newitem = null;

                if (text != null) {
                    newitem = text.trim();
                } else
                    return;

                if (!newitem.isEmpty()) {
                    model.remove(index);
                    model.add(index, newitem);
                }
            }
        });

        removeall.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    model.clear();
                }
        });

        rightPanel.add(add);
        rightPanel.add(Box.createRigidArea(new Dimension(0,4)));
        rightPanel.add(rename);
        rightPanel.add(Box.createRigidArea(new Dimension(0,4)));
        rightPanel.add(delete);
        rightPanel.add(Box.createRigidArea(new Dimension(0,4)));
        rightPanel.add(removeall);

        rightPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20));

        panel.add(leftPanel);
        panel.add(rightPanel);

        add(panel);

        setSize(350, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new List();
    }
}
</pre>

<p>
The example shows a list component and four buttons. The buttons control 
the data in the list component. 
The example is a bit larger, because we did some additional checks there. 
We do not allow to input empty spaces into the 
list component. 
</p>

<pre class="explanation">
model = new DefaultListModel();
model.addElement("Amelie");
model.addElement("Aguirre, der Zorn Gottes");
...
</pre>

<p>
We create a list model and add elements into it.
</p>

<pre class="explanation">
list = new JList(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
</pre>

<p>
We create a list component. The parameter of the constructor is the model, we have created. 
We put the list into the single selection mode. We also put some space around the list. 
</p>


<pre class="explanation">
if (text != null) 
    item = text.trim();
else 
    return;

if (!item.isEmpty())
    model.addElement(item);
</pre>

<p>
We add only items that are not equal to null and are not empty. e.g. 
that contain at least one character other than white
space. It makes no sense to add white spaces or null values into the list. 
</p>

<pre class="explanation">
ListSelectionModel selmodel = list.getSelectionModel();
int index = selmodel.getMinSelectionIndex();
if (index >= 0)
    model.remove(index);
</pre>

<p>
This is the code, that runs when we press the delete button. In order 
to delete an item from the list, it must
be selected. So we must figure out the currently selected item. For 
this, we call the <code>getSelectionModel()</code> method.
This is a GUI work, so we use a <code>ListSelectionModel</code>. 
Removing an item is working with data. For that we use the list data model.
</p>

<p>
So, in our example we used both list models. We called add(), remove() and 
clear() methods of the list data model to work with our data. And we used a list 
selection model in order to find out the selected item, which is a GUI job. 
</p>


<img src="/img/gui/javaswing/listmodels.png" alt="List Models">
<div class="figure">Figure: List Models</div>


<h2>A document model</h2>

<p>
This is an excellent example of a separation of a data from the visual 
representation. In a <code>JTextPane</code> component, we have 
a <code>StyledDocument</code> for setting the style of the text data. 
</p>


<pre class="code">
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;


public class DocumentModel extends JFrame {

    private StyledDocument doc;
    private JTextPane textpane;

    public DocumentModel() {

        setTitle("Document Model");

        JToolBar toolbar = new JToolBar();

        ImageIcon bold = new ImageIcon("bold.png");
        ImageIcon italic = new ImageIcon("italic.png");
        ImageIcon strike = new ImageIcon("strike.png");
        ImageIcon underline = new ImageIcon("underline.png");

        JButton boldb = new JButton(bold);
        JButton italb = new JButton(italic);
        JButton strib = new JButton(strike);
        JButton undeb = new JButton(underline);

        toolbar.add(boldb);
        toolbar.add(italb);
        toolbar.add(strib);
        toolbar.add(undeb);

        add(toolbar, BorderLayout.NORTH);

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        JScrollPane pane = new JScrollPane();
        textpane = new JTextPane(); 
        textpane.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        doc = textpane.getStyledDocument();

        Style style = textpane.addStyle("Bold", null);
        StyleConstants.setBold(style, true);

        style = textpane.addStyle("Italic", null);
        StyleConstants.setItalic(style, true);

        style = textpane.addStyle("Underline", null);
        StyleConstants.setUnderline(style, true);

        style = textpane.addStyle("Strike", null);
        StyleConstants.setStrikeThrough(style, true);


        boldb.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doc.setCharacterAttributes(textpane.getSelectionStart(), 
                    textpane.getSelectionEnd() - textpane.getSelectionStart(),
                    textpane.getStyle("Bold"), false);
            }
        });

        italb.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doc.setCharacterAttributes(textpane.getSelectionStart(), 
                    textpane.getSelectionEnd() - textpane.getSelectionStart(),
                    textpane.getStyle("Italic"), false);
            }

        });

        strib.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doc.setCharacterAttributes(textpane.getSelectionStart(), 
                    textpane.getSelectionEnd() - textpane.getSelectionStart(),
                    textpane.getStyle("Strike"), false);
            }

        });

        undeb.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doc.setCharacterAttributes(textpane.getSelectionStart(), 
                    textpane.getSelectionEnd() - textpane.getSelectionStart(),
                    textpane.getStyle("Underline"), false);
            }
        });

        pane.getViewport().add(textpane);
        panel.add(pane);

        add(panel);

        setSize(new Dimension(380, 320));
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new DocumentModel();
    }
}
</pre>

<p>
The example has a text pane and a toolbar. In the toolbar, we have four 
buttons, that change attributes of the text.
</p>

<pre class="explanation">
doc = textpane.getStyledDocument();
</pre>

<p>
Here we get the styled document, which is a model for the text pane component. 
</p>

<pre class="explanation">
Style style = textpane.addStyle("Bold", null);
StyleConstants.setBold(style, true);
</pre>

<p>
A style is a set of text attributes, such as color, size. Here we register 
a bold style for the text pane component. The registered styles can be 
retrieved at any time. 
</p>

<pre class="explanation">
doc.setCharacterAttributes(textpane.getSelectionStart(), 
    textpane.getSelectionEnd() - textpane.getSelectionStart(),
    textpane.getStyle("Bold"), false);
</pre>

<p>
Here we change the attributes of the text. The parameters are the offset, 
length of the selection, the style and the boolean value replace. The offset 
is the beginning of the text, where we apply the bold text. We get the length 
value by substracting the selection end and selection start values. Boolean 
value false means, we are not replacing an old style with a new one, but we 
merge them. This means, if the text is underlined and we make it bold, the 
result is an underlined bold text. 
</p>


<img src="/img/gui/javaswing/documentmodel.png" alt="Document model">
<div class="figure">Figure: Document model</div>


<p>
In this chapter, we have mentioned Swing models. 
</p>


<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified November 4, 2007  <span class="copyright">&copy; 2007 - 2013 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>